/*
 * Copyright 2022-2023 the original author(https://github.com/wj596)
 * 
 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * </p>
 */
package org.jsets.fastboot.flowable.service.impl;

import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import org.flowable.engine.TaskService;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.identitylink.api.IdentityLink;
import org.flowable.task.api.Task;
import org.jsets.fastboot.flowable.model.entity.CompositeElement;
import org.jsets.fastboot.flowable.model.entity.FlowTask;
import org.jsets.fastboot.flowable.model.entity.TaskTrace;
import org.jsets.fastboot.flowable.config.FlowableConfig;
import org.jsets.fastboot.flowable.mapper.FlowTaskMapper;
import org.jsets.fastboot.flowable.model.dto.CompleteTaskParam;
import org.jsets.fastboot.flowable.model.dto.CompleteTaskResult;
import org.jsets.fastboot.flowable.model.dto.FlowTaskQuery;
import org.jsets.fastboot.flowable.model.dto.FlowTaskResult;
import org.jsets.fastboot.flowable.service.ICompositeElementService;
import org.jsets.fastboot.flowable.service.IFlowEngineService;
import org.jsets.fastboot.flowable.service.IFlowTaskService;
import org.jsets.fastboot.flowable.service.ITaskTraceService;
import org.jsets.fastboot.security.IAuthenticatedUserProvider;
import org.jsets.fastboot.util.CollectionUtils;
import org.jsets.fastboot.util.SpringContextHolder;
import org.jsets.fastboot.util.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import lombok.extern.slf4j.Slf4j;

/**
 * @author wangjie (https://github.com/wj596)
 * @date 2021.06.25 11:23
 */
@Slf4j
@Service
public class FlowTaskService implements IFlowTaskService {

	@Autowired
	private IFlowEngineService flowEngine;
	@Autowired
	private FlowTaskMapper flowTaskMapper;
	@Autowired
	private ITaskTraceService traceService;
	@Autowired
	private ICompositeElementService elementService;
	
	@Override
	public CompleteTaskResult complete(CompleteTaskParam param) {
		TaskService taskService = this.flowEngine.getTaskService();
		Task task = taskService.createTaskQuery().taskId(param.getTaskId()).singleResult();
		Objects.requireNonNull(task, "找不到该任务或该任务已办结");
    	String instanceId = task.getProcessInstanceId();
		TaskTrace taskTrace = new TaskTrace();	
		taskTrace.setInstanceId(instanceId);
		taskTrace.setTaskId(param.getTaskId());
		taskTrace.setTaskName(task.getName());
		taskTrace.setTaskDefId(task.getTaskDefinitionId());
		taskTrace.setComment(param.getComment());
		taskTrace.setResult(param.getResult());
		taskTrace.setCompleteType(FlowableConfig.COMPLETE_TYPE_MANUAL);
		taskTrace.setCompleteTime(new Date());
		SpringContextHolder.tryBeanThen(IAuthenticatedUserProvider.class, p->{
			taskTrace.setUsername(p.getCurrentUsernameIfLogined());
		});
		this.traceService.save(taskTrace);
		
		Map<String,Object> variables = Maps.newHashMap();
		if (CollectionUtils.notEmpty(param.getVariables())) {
			variables.putAll(param.getVariables());
		}
		variables.put(FlowableConfig.HANDLE_RESULT_KEY, FlowableConfig.HANDLE_RESULT_PASS.equals(param.getResult())?1:9);
		if(StringUtils.notEmpty(param.getUsername())) {
			variables.put(FlowableConfig.PROCESS_LAST_ASSIGNEE_KEY, param.getUsername());
		}else {
			variables.put(FlowableConfig.PROCESS_LAST_ASSIGNEE_KEY, task.getAssignee());
		}
		taskService.complete(param.getTaskId(), variables);

		CompleteTaskResult result = new CompleteTaskResult();
		result.setInstanceId(instanceId);
		List<Task> tasks = taskService.createTaskQuery().processInstanceId(instanceId).list();
		if (CollectionUtils.isEmpty(tasks)) {
			result.setInstanceEnded(true);
			return result;
		}

		ProcessInstance processInstance  = this.flowEngine.getRuntimeService().createProcessInstanceQuery().processInstanceId(instanceId).singleResult();
		if(Objects.nonNull(processInstance)) {
			for(Task next : tasks) {
				CompositeElement compositeElement = this.elementService.getByDeploymentId(processInstance.getDeploymentId(), next.getTaskDefinitionKey());
				if(this.isMergeWithStartUser(processInstance, next, compositeElement)) {
					this.autoComplete(next.getId(), "审批人与发起人相同", variables);
				}
				
				if(this.isMergeWithPrevious(processInstance, next, compositeElement)) {
					this.autoComplete(next.getId(), "审批人与上步审批人相同", variables);
				}
			}
		}

		tasks = taskService.createTaskQuery().processInstanceId(task.getProcessInstanceId()).list();
		result.setInstanceEnded(tasks.size()==0);
		List<FlowTaskResult> nexts = Lists.newArrayList();
		tasks.forEach(t->{
			FlowTaskResult next = new FlowTaskResult();
			next.setId(t.getId());
			next.setName(t.getName());
			next.setAssignee(t.getAssignee());
			List<IdentityLink> links = taskService.getIdentityLinksForTask(t.getId());
			List<String> candidateUsers = Lists.newArrayList();
			List<String> gandidateGruops = Lists.newArrayList();
			links.forEach(link->{
				candidateUsers.add(link.getUserId());
			});
			links.forEach(link->{
				candidateUsers.add(link.getGroupId());
			});
			next.setCandidateUsers(candidateUsers);
			next.setCandidateGruops(gandidateGruops);
			nexts.add(next);
		});
		result.setNexts(nexts);
		return result;
	}
	
	@Override
	public void autoComplete(String taskId, String reason, Map<String,Object> params) {
		TaskService taskService = this.flowEngine.getTaskService();
		Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
		if(Objects.nonNull(task)) {
			TaskTrace taskTrace = new TaskTrace();	
			taskTrace.setInstanceId(task.getProcessInstanceId());
			taskTrace.setTaskId(taskId);
			taskTrace.setTaskName(task.getName());
			taskTrace.setTaskDefId(task.getTaskDefinitionId());
			taskTrace.setComment(reason);
			taskTrace.setResult(FlowableConfig.HANDLE_RESULT_PASS);
			taskTrace.setCompleteType(FlowableConfig.COMPLETE_TYPE_AUTO);
			taskTrace.setCompleteTime(new Date());
			this.traceService.save(taskTrace);
			
			Map<String,Object> variables = Maps.newHashMap();
			variables.putAll(params);
			variables.put(FlowableConfig.HANDLE_RESULT_KEY, FlowableConfig.HANDLE_RESULT_PASS);
			variables.put(FlowableConfig.PROCESS_LAST_ASSIGNEE_KEY, task.getAssignee());
			taskService.complete(taskId, variables);
			
			List<Task> tasks = taskService.createTaskQuery().processInstanceId(task.getProcessInstanceId()).list();
			ProcessInstance processInstance  = this.flowEngine.getRuntimeService().createProcessInstanceQuery().processInstanceId(task.getProcessInstanceId()).singleResult();
			System.out.println("------1: "+processInstance);
			if(Objects.nonNull(processInstance)) {
				for(Task next : tasks) {
					System.out.println("------2: "+next.getName());
					CompositeElement compositeElement = this.elementService.getByDeploymentId(processInstance.getDeploymentId(), next.getTaskDefinitionKey());
					System.out.println("------3: "+compositeElement);
					if(this.isMergeWithStartUser(processInstance, next, compositeElement)) {
						this.autoComplete(next.getId(), "审批人与发起人相同", variables);
					}
					
					if(this.isMergeWithPrevious(processInstance, next, compositeElement)) {
						this.autoComplete(next.getId(), "审批人与上步审批人相同", variables);
					}
				}
			}
		}
	}

	@Override
	public boolean turnTask(String taskId, String targetUser) {
		this.flowEngine.getTaskService().setAssignee(taskId, targetUser);
		return true;
	}

	@Override
	public boolean delegateTask(String taskId, String targetUser) {
		this.flowEngine.getTaskService().delegateTask(taskId, targetUser);
		return true;
	}

	@Override
	public List<FlowTask> selectList(FlowTaskQuery criteria) {
		return this.flowTaskMapper.selectList(criteria);
	}
	
	@Override
	public Page<FlowTask> selectPage(FlowTaskQuery criteria) {
		Page<FlowTask> page = new Page<FlowTask>();
		page.setCurrent(criteria.getCurrent());
		page.setSize(criteria.getSize());
		List<FlowTask> list = this.flowTaskMapper.selectPage(page, criteria);
		page.setRecords(list);
		return page;
	}
	
	private boolean isMergeWithStartUser(ProcessInstance processInstance, Task next, CompositeElement compositeElement) {
		if(Objects.isNull(compositeElement)) {
			return false;
		}
		if(!compositeElement.getMergeWithStartUser()) {
			return false;
		}
		
		String startUserId = processInstance.getStartUserId();
		if(StringUtils.isBlank(startUserId)) {
			return false;
		}
		
		System.out.println("------4: "+startUserId);
		
		String nextAssignee = next.getAssignee();
		if(StringUtils.isBlank(nextAssignee)) {
			return false;
		}
		
		return nextAssignee.equals(startUserId);
	}
	
	private boolean isMergeWithPrevious(ProcessInstance processInstance, Task next, CompositeElement compositeElement) {
		if(Objects.isNull(compositeElement)) {
			return false;
		}
		if(!compositeElement.getMergeWithPrevious()) {
			return false;
		}
		
		Map<String,Object> processVariables = processInstance.getProcessVariables();
		if(CollectionUtils.isEmpty(processVariables)) {
			return false;
		}
		
		if(Objects.isNull(processVariables.get(FlowableConfig.PROCESS_LAST_ASSIGNEE_KEY))) {
			return false;
		}
		
		String lastAssignee  = (String)processVariables.get(FlowableConfig.PROCESS_LAST_ASSIGNEE_KEY);
		if(StringUtils.isBlank(lastAssignee)) {
			return false;
		}
		
		String nextAssignee = next.getAssignee();
		if(StringUtils.isBlank(nextAssignee)) {
			return false;
		}
		
		return nextAssignee.equals(lastAssignee);
	}

}